package net.gdface.cassdk;

import net.gdface.license.GfLicenseProvider;
import net.gdface.license.LicenseManager;
import net.gdface.license.LicenseUtils;
import net.gdface.utils.Judge;
import net.gdface.utils.SampleLog;

import static net.gdface.cassdk.CasAndroidConfigProvider.*;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * CASSDK for android/arm JNI 接口<br>
 * 应用层可以通过SPI加载机制提供授权信息,
 * 也可以通过{@link #setLicenseKey(String)},{@link #setLicenseCode(String)}方法提供授权信息<br>
 * 此类为非线程安全的，不可多线程共享调用，需要在外部进一步封装实现线程安全
 * @author guyadong
 *
 */
public class CasAndroidArmBridge {
	/**
	 * 应用层提供的{@link CasAndroidConfigProvider}接口实例,未提供则初始化为{@link DefaultCasAndroidConfig}实例
	 */
	public static final CasAndroidConfigProvider CONFIG = getConfigProvider();
	/** 授权管理器接口实例 */ 
	public static final LicenseManager LICENSE_MANAGER  = LicenseUtils.getLicenseManager();
	final long[] detectHandle = new long[1];
	final long[] featureHandle = new long[1];
	/**
	 * 初始化状态
	 */
	private SdkStatus status=SdkStatus.SDK_UNINITIALIZED;
	static {
		try {
			// 加载算法动态库
			System.loadLibrary("FS_AndroidFaceSDK");
		} catch (Exception e) {
			SampleLog.log(e.getMessage());
			throw new ExceptionInInitializerError(e);
		}
		// 要在动态库加载后调用，因为方法中可能会调用动态库中的函数
		LICENSE_MANAGER.installLicenseIfSPIAvailable();
	}

	CasAndroidArmBridge() {
	}
	/**
	 * SPI(Service Provider Interface)机制加载 {@link GfLicenseProvider}实例,
	 * 没有找到返回{@link DefaultCasAndroidConfig}实例
	 * @return
	 */
	private static CasAndroidConfigProvider getConfigProvider() {		
		ServiceLoader<CasAndroidConfigProvider> providers = ServiceLoader.load(CasAndroidConfigProvider.class);
		Iterator<CasAndroidConfigProvider> itor = providers.iterator();
		if(!itor.hasNext()){
			return new DefaultCasAndroidConfig();
		}
		return itor.next();
	}

	/**
	 * 返回当前SDK初始化状态
	 * @return
	 */
	SdkStatus getStatus() {
		return status;
	}

	/**
     * 如果input非0结尾则添加'\0'返回,否则返回input
     * @param input
     * @return 
     */
    private static String zeroEnd(String input){
    	if(input == null){
    		return null;
    	}
		if(input.endsWith("\0")){
			return input;
		}    		
    	return input+"\0";
    }
	/**
	 * 执行SDK(检测模块)初始化
	 * @param licenseKey 授权关键字
	 * @param licenseCode 授权码
	 * @param instance
	 * @return 初始化状态
	 */
	static SdkStatus fdInit(String licenseKey, String licenseCode,CasAndroidArmBridge instance) {
		if(SdkStatus.SDK_INIT_OK  == instance.status){
			return instance.status;
		}
		licenseCode = zeroEnd(licenseCode);
		String szFilePath = "\0";
		String szCompanyNames = zeroEnd(licenseKey);
		// 检测模块初始化
		int fdFlag = FDInit(licenseCode.getBytes(), 
				szCompanyNames.getBytes(), 
				szFilePath.getBytes(), 
				CONFIG.getProcessMinl(), 
				CONFIG.getProcessMaxl(), 
				CONFIG.getDetectThreshold(), 
				instance.detectHandle);
		return SdkStatus.jniCode(fdFlag);
	}
	/**
	 * 执行SDK(识别模块)初始化
	 * @param licenseKey 授权关键字
	 * @param licenseCode 授权码
	 * @return 初始化状态
	 */
	private static SdkStatus ffInit(String licenseKey, String licenseCode,CasAndroidArmBridge instance) {
		if(SdkStatus.SDK_INIT_OK  == instance.status){
			return instance.status;
		}
		licenseCode = zeroEnd(licenseCode);
		String szFilePath = "\0";
		String szCompanyNames = zeroEnd(licenseKey);
		int ffFlag = FFInit(licenseCode.getBytes(), 
				szCompanyNames.getBytes(), 
				szFilePath.getBytes(), 
				instance.featureHandle);
		return SdkStatus.jniCode(ffFlag);
	}
	/**
	 * 执行SDK(检测模块,识别模块)初始化,初始化失败则抛出异常
	 * @param licenseKey 授权关键字
	 * @param licenseCode 授权码
	 * @throws SdkInitException 
	 */
	private void sdkInit(String licenseKey, String licenseCode) throws SdkInitException {
		if(SdkStatus.SDK_INIT_OK  == status){
			return;
		}
		SdkStatus fdStatus = SdkStatus.SDK_UNINITIALIZED;
		SdkStatus ffStatus = SdkStatus.SDK_UNINITIALIZED;
		try{
			fdStatus  = fdInit(licenseKey, licenseCode, this);
			if(fdStatus !=SdkStatus.SDK_INIT_OK){
				status = fdStatus;
				SampleLog.log("detect module: {}", status.msg);
				throw new SdkInitException(status);
			}
			ffStatus  = ffInit(licenseKey, licenseCode, this);
			if(ffStatus !=SdkStatus.SDK_INIT_OK){
				status = ffStatus;
				SampleLog.log("feature module: {}", status.msg);
				throw new SdkInitException(status);
			}
			// 检测模块和识别模块都初始化成功则置状态为成功
			status = SdkStatus.SDK_INIT_OK;	
		}finally {
			// 如果没有完全初始化成功，则destroy已经初始化模块
			if(status != SdkStatus.SDK_INIT_OK){
				if(fdStatus !=SdkStatus.SDK_INIT_OK){
					FDDestroy(detectHandle[0]);
				}
				if(ffStatus !=SdkStatus.SDK_INIT_OK){
					FFDestroy(featureHandle[0]);
				}
			}
		}
	}
    
	/**
	 * SDK初始化<br>
	 * 初始化前{@link #licenseKey}和{@link licenseCode}必须已经初始化
	 * @return
	 * @throws SdkInitException
	 */
	CasAndroidArmBridge init() throws SdkInitException{
		String licenseKey = LICENSE_MANAGER.getLicenseKey();
		String licenseCode = LICENSE_MANAGER.getLicenseCode();

		if(Judge.isEmpty(licenseKey)){
			throw new SdkInitException("EMPTY licenseKey,must call setLicenseKey() firstly");
		}
		if(Judge.isEmpty(licenseCode)){
			throw new SdkInitException("EMPTY licenseCode,must call setLicenseCode() firstly");
		}
		sdkInit(licenseKey, licenseCode);
		return this;
	}
    /**
	 * 人脸识别SDK资源释放
	 * @see #FDDestroy(long)
	 * @see #FFDestroy(long)
	 */
	void destroy() {	
		if(SdkStatus.SDK_INIT_OK  == status){
			status = SdkStatus.SDK_UNINITIALIZED;
	        FDDestroy(detectHandle[0]);
	        FFDestroy(featureHandle[0]);
		}
    }
    public int detect(byte[] BGR, int width, int height, double[] rect){
    	int ret = FDDetect(detectHandle[0], BGR, width, height, rect);
    	if(ret<0){
    		throw new SdkRuntimeException(SdkStatus.jniCode(ret));
    	}
    	return ret;
    }
	public double[] feaExtract(byte[] BGR, int width, int height, double[] rect){
		double[] buffer = new double[FEATURE_LEN];
		int ret = FFFeaExtract(featureHandle[0], BGR, width, height, buffer, rect);
		if(ret<0){
			throw new SdkRuntimeException(SdkStatus.jniCode(ret));
		}
		return buffer;
	}
	public byte[] feaExtractByte(byte[] BGR, int width, int height, double[] rect){
		byte[] buffer = new byte[FEATURE_BYTES];
		int ret = FFFeaExtractByte(featureHandle[0], BGR, width, height, buffer, rect);
		if(ret<0){
			throw new SdkRuntimeException(SdkStatus.jniCode(ret));
		}
		return buffer;
	}
	/**
     * 获取设备加密信息
     * @param encryptionSerial [out]获取的设备加密信息
     * @param licenseKey 授权关键字
     * @return 成功(>0 加密信息长度)或错误代码(< 0)
     */
    static native int FSGetDevicesSerial(byte[] encryptionSerial, byte[] licenseKey);

    /**
     * 人脸检测初始化函数
     * @param licenseCode 授权码
     * @param licenseKey 授权关键字
     * @param path 授权码本地路径
     * @param ProcessMinL 检测时，短边缩放 建议 360
     * @param ProcessMaxL 检测时，长边缩放尺寸 建议 640
     * @param detectThresld 检测阈值 建议 0.7
     * @param handle 结构体
     * @return 成功(0)或错误代码(< 0),see also {@link SdkStatus}
     */
    private static native int FDInit(byte[] licenseCode, byte[] licenseKey, byte[] path, int ProcessMinL, int ProcessMaxL, double detectThresld, long[] handle);

    /** 
     * 人脸检测模块资源释放函数
     * @param handle
     */
    static native void FDDestroy(long handle);

    /**
     * 对输入图像进行人脸检测,检测结果由rect返回,
     * 检测到人脸返回>0的人脸个数
     * @param handle
     * @param BGR 待检测图像BGR格式
     * @param width 图像宽度
     * @param height 图像调试
     * @param rect 用于返回人脸位置及关键点信 息,大小在外部申请 (61 * MAX_FACE_COUNT)<br>
     * 							rect格式：61 个数据为一组，依次为 人脸坐标(left, top, width, height) 
     * 							人脸质量 人脸姿态(roll yaw pitch) 人脸清晰度 人脸亮度 人脸关键点（25*2）
     * 							人脸检测框置信度
     * 
     * @return 检测到的人脸个数(>0)或者错误代码(< 0),see also {@link SdkStatus}
     */
    private static native int FDDetect(long handle, byte[] BGR, int width, int height, double[] rect);

    /**
     * @return 人脸检测模块版本
     */
    public static native String FDgetVersion();


    /**
     * 人脸识别初始化函数
     * @param licenseCode 授权码
     * @param licenseKey 授权关键字
     * @param path 授权码本地路径
     * @param handle 句柄
     * @return 成功(0)或错误代码(< 0),see also {@link SdkStatus}
     */
    private static native int FFInit(byte[] licenseCode, byte[] licenseKey, byte[] path, long[] handle);

    /**
     * 人脸识别模块资源释放函数
     * @param handle 句柄
     */
    private static native void FFDestroy(long handle);

    /**
     * 对输入图像中检测到的人脸提取特征
     * @param handle 操作句柄
     * @param BGR 待检测图像BGR格式
     * @param width 图像宽度
     * @param height 图像高度
     * @param fea [out] 人脸特征 空间在外部申请，长度512
     * @param rect 人脸位置及关键点信息 由人脸检测SDK 产生,参见 {@link #FDDetect(long, byte[], int, int, double[])}
     * @return 成功(0)或错误代码(< 0),see also {@link SdkStatus}
     */
    private static native int FFFeaExtract(long handle, byte[] BGR, int width, int height, double[] fea, double[] rect);

    /**
     * 人脸相似度比对函数<br>
     * 对人脸特征feaA和feaB进行相似度比对，返回相似度结果
     * @param feaA
     * @param feaB
     * @return 相似度(0.0~1.0)
     */
    public static native double FFSimilarity(double[] feaA, double[] feaB);
    /**
     * 对输入图像中检测到的人脸提取特征
     * @param handle 操作句柄
     * @param BGR 待检测图像BGR格式
     * @param width 图像宽度
     * @param height 图像高度
     * @param fea [out] 人脸特征 空间在外部申请，长度4096
     * @param rect 人脸位置及关键点信息 由人脸检测SDK 产生,参见 {@link #FDDetect(long, byte[], int, int, double[])}
     * @return 成功(0)或错误代码(< 0),see also {@link SdkStatus}
     */
    private static native int FFFeaExtractByte(long handle, byte[] BGR, int width, int height, byte[] fea, double[] rect);

    /**
     * 人脸相似度比对函数<br>
     * 对人脸特征feaA和feaB进行相似度比对，返回相似度结果
     * @param feaA
     * @param feaB
     * @return 相似度(0.0~1.0)
     */
    public static native double FFSimilarityByte(byte[] feaA, byte[] feaB);

    /**
     * @return 人脸识别模块版本
     */
    public static native String FFgetVersion();

}
